//
// Manta - Structural Variant and Indel Caller
// Copyright (c) 2013-2019 Illumina, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//

/// \file
/// \author Chris Saunders
///

#pragma once

#include "blt_util/blt_types.hpp"
#include "blt_util/pos_range.hpp"
#include "blt_util/reference_contig_segment.hpp"

#include <cstring>

#include <iterator>
#include <string>

namespace BASE_ID {
enum index_t { A, C, G, T, ANY, SIZE };
}

enum { N_BASE = 4 };

void base_error(const char* func, const char a);

inline uint8_t base_to_id(const char a)
{
  using namespace BASE_ID;
  switch (a) {
  case 'A':
    return A;
  case 'C':
    return C;
  case 'G':
    return G;
  case 'T':
    return T;
  case 'N':
    return ANY;
  default:
    base_error("base_to_id", a);
    return 4;
  }
}

void id_to_base_error(const uint8_t i);

inline char id_to_base(const uint8_t i)
{
  static const char base[] = "ACGTN";

  if (i > N_BASE) id_to_base_error(i);
  return base[i];
}

/// valid in the ELAND sense [ACGTN]
inline bool is_valid_base(char a)
{
  switch (a) {
  case 'A':
  case 'C':
  case 'G':
  case 'T':
  case 'N':
    return true;
  default:
    return false;
  }
}

inline bool is_iupac_base(char a)
{
  switch (a) {
  case 'A':
  case 'C':
  case 'G':
  case 'U':
  case 'T':
  case 'R':
  case 'Y':
  case 'S':
  case 'W':
  case 'K':
  case 'M':
  case 'B':
  case 'D':
  case 'H':
  case 'V':
  case '.':
  case '-':
  case 'N':
    return true;
  default:
    return false;
  }
}

/// valid in the ELAND sense [ACGTN]
bool is_valid_seq(const char* seq);

inline char elandize_base(char a)
{
  switch (a) {
  case 'A':
    return 'A';
  case 'C':
    return 'C';
  case 'G':
    return 'G';
  case 'U':
  case 'T':
    return 'T';
  case 'R':
  case 'Y':
  case 'S':
  case 'W':
  case 'K':
  case 'M':
  case 'B':
  case 'D':
  case 'H':
  case 'V':
  case '.':
  case '-':
  case 'N':
    return 'N';
  default:
    base_error("elandize_base", a);
    return 'N';
  }
}

inline char comp_base(char a)
{
  switch (a) {
  case 'A':
    return 'T';
  case 'C':
    return 'G';
  case 'G':
    return 'C';
  case 'T':
    return 'A';
  case 'N':
    return 'N';
  default:
    base_error("comp_base", a);
    return 'N';
  }
}

inline char get_seq_base(const char* seq, const pos_t size, const pos_t pos)
{
  if ((pos < 0) || (pos >= size)) {
    return 'N';
  } else {
    return seq[pos];
  }
}

inline char get_seq_base(const std::string& seq, const pos_t pos)
{
  return get_seq_base(seq.c_str(), seq.size(), pos);
}

// generalized in-place revcomp -- requires bidirectional iterators
//
template <typename Iter>
void reverseComp(Iter b, Iter e)
{
  char t;
  for (; b != e; ++b) {
    if ((--e) == b) {
      *b = comp_base(*b);
      break;
    }
    t  = comp_base(*b);
    *b = comp_base(*e);
    *e = t;
  }
}

// easy string version:
inline void reverseCompStr(std::string& seq)
{
  reverseComp(seq.begin(), seq.end());
}

template <typename T>
void fixCstring(T)
{
}
inline void fixCstring(char* b)
{
  *b = '\0';
}

// generalized copy revcomp -- requires bidirectional iterators
//
template <typename ConstIter, typename Iter>
void reverseCompCopy(ConstIter cb, ConstIter ce, Iter b)
{
  while (cb != ce) {
    *b++ = comp_base(*--ce);
  }
  fixCstring(b);
}

// easy char*->string version:
inline std::string reverseCompCopyCStr(const char* str)
{
  std::string result;
  reverseCompCopy(str, str + strlen(str), std::back_insert_iterator<std::string>(result));
  return result;
}

// easy string->string version:
inline std::string reverseCompCopyStr(const std::string& seq)
{
  std::string result;
  reverseCompCopy(seq.begin(), seq.end(), std::back_insert_iterator<std::string>(result));
  return result;
}

/// Standardize reference sequence to [ACGTN]. Fail when non-IUPAC
/// character is found.
void standardize_ref_seq(
    const char* ref_seq_file, const char* chr_name, std::string& ref_seq, const pos_t offset);

std::size_t get_ref_seq_known_size(const reference_contig_segment& ref_seq, const pos_range pr);

/// Looks for the smallest possible perfect repeat in seq
///
void get_seq_repeat_unit(const std::string& seq, std::string& repeat_unit, unsigned& repeat_count);

/// Same as above but removes first base from seq
///
void get_vcf_seq_repeat_unit(const std::string& seq, std::string& repeat_unit, unsigned& repeat_count);
